
#ifndef ATLCIMAGE_IO_PRIVATE_H
#define ATLCIMAGE_IO_PRIVATE_H

#include <boost/static_assert.hpp>

#include "atlimage.h"
#include <vector>

namespace boost { namespace gil {

namespace detail {

template <typename Channel,typename ColorSpace>
struct cimage_read_support_private {
    BOOST_STATIC_CONSTANT(bool,is_supported=false);
};

template <> 
struct cimage_read_support_private<bits8, rgb_t> {
	BOOST_STATIC_CONSTANT(bool,is_supported=true);
};

template <typename Channel,typename ColorSpace>
struct cimage_write_support_private {
    BOOST_STATIC_CONSTANT(bool,is_supported=false);
};

template <>
struct cimage_write_support_private<bits8, rgb_t> {
	BOOST_STATIC_CONSTANT(bool,is_supported=true);
};

//
class cimage_reader {
protected:
	ATL::CImage m_Image;

	void init(const tstring& filename)	{
		io_error_if( m_Image.Load(filename.c_str())!=S_OK, "cimage_reader: failed to load file");
	}
	
	template <typename View>
	void apply_non_indexed(const View& view)
	{
		int BitsPerPixel = m_Image.GetBPP();
		io_error_if( (BitsPerPixel != 16) && (BitsPerPixel != 24) && (BitsPerPixel != 32), "cimage_reader: unexpected bits-per-pixel value" );
		int BytesPerPixel = BitsPerPixel / 8;
		int Pitch = m_Image.GetPitch();
		assert( Pitch != 0 );
		void const* const pBits = m_Image.GetBits();
		for( int y = 0; y < m_Image.GetHeight(); y++)
		{
			BYTE const* const pRow = (BYTE*)pBits + Pitch*y; // source
			View::x_iterator itX = view.row_begin(y); // destination

			for( int x = 0; x < m_Image.GetWidth(); x++)
			{
				BYTE* pPixel = (BYTE*)pRow + BytesPerPixel*x;
				BYTE b, g, r ;
				if( (BitsPerPixel == 24) || (BitsPerPixel == 32) )
				{
					b = *pPixel;
					g = *(pPixel + 1);
					r = *(pPixel + 2);
				}
				else
				{
					b = (*(WORD*)pPixel) & 0x001F;
					g = (*(WORD*)pPixel) & 0x03E0;
					r = (*(WORD*)pPixel) & 0x7C00;
				}
				itX[x] = pixel<bits8,layout<rgb_t> >(r, g, b);
			}
		}

	}

	template <typename View>
	void apply_indexed(const View& view)
	{
		int BitsPerPixel = m_Image.GetBPP();
		io_error_if( (BitsPerPixel != 1) && (BitsPerPixel != 4) && (BitsPerPixel != 8), "cimage_reader: unexpected bits-per-pixel value" );
		// get color table
		int ColorTableEntriesCount = m_Image.GetMaxColorTableEntries();
		RGBQUAD* pColorTable = new RGBQUAD[ColorTableEntriesCount];
		ZeroMemory( pColorTable, sizeof(RGBQUAD)*ColorTableEntriesCount );
		m_Image.GetColorTable( 0, ColorTableEntriesCount, pColorTable);
		//
		int Pitch = m_Image.GetPitch();
		assert( Pitch != 0 );
		void const* const pBits = m_Image.GetBits();
		
		BYTE Index = 0; // index of color in the color table
		BYTE b, g, r ;
		// used only for less than 8 bits per index 
		const BYTE BufferSize_InBits = 8;
		BYTE BitsRead = 0;
		BYTE Buffer = 0;
		BYTE Mask = (BitsPerPixel == 1) ? 0x80:0xF0; // *crazy*
		//
		for( int y = 0; y < m_Image.GetHeight(); y++)
		{
			BYTE const* const pRow = (BYTE*)pBits + Pitch*y; // source
			View::x_iterator itX = view.row_begin(y); // destination
			
			BitsRead = BufferSize_InBits; // in order to refresh buffer first time 
			for( int x = 0; x < m_Image.GetWidth(); x++)
			{
				if( BitsPerPixel == 8 )
				{
					
					Index = *(pRow + x);
				}
				else
				{
					if( BitsRead >= BufferSize_InBits )
					{
						Buffer = *(pRow + (x*BitsPerPixel/BufferSize_InBits)); // read new piece of data
						BitsRead = 0;
					}
					Index = Buffer & Mask;
					Index = Index >> ((BitsPerPixel == 1)? 7:4); // *crazy*

					Buffer = Buffer << BitsPerPixel;
					BitsRead += BitsPerPixel;
				}
				assert( (Index >=0) && (Index < ColorTableEntriesCount) );
				b = pColorTable[Index].rgbBlue;
				g = pColorTable[Index].rgbGreen;
				r = pColorTable[Index].rgbRed;
				itX[x] = pixel<bits8,layout<rgb_t> >(r, g, b);
			}
		}
		delete[] pColorTable;
	}
public:
	cimage_reader(const tstring& filename) { init(filename); }

	template <typename View>
	void apply(const View& view){
		io_error_if(view.dimensions() != get_dimensions(), "cimage_reader::apply(): input view dimensions do not match the image file");
		if( m_Image.IsIndexed() )
			apply_indexed(view);
		else
			apply_non_indexed(view);
	}

	template <typename Image>
    void read_image(Image& im) {
        im.recreate(get_dimensions());
        apply(view(im));
    }
	
	point2<std::ptrdiff_t> get_dimensions() const {
		return point2<std::ptrdiff_t>(m_Image.GetWidth(), m_Image.GetHeight());
    }
};
//

class cimage_writer {
protected:
	ATL::CImage m_Image;
	tstring m_FileName;
public:
	cimage_writer(const tstring& filename):m_FileName(filename){
	};

	template <typename View>
    void apply(const View& view, REFGUID guidFileType) {
		const int cBytesPerPixel = 4; // may be I should include this value in 'write_support' structure?
		const int cBitsPerPixel = cBytesPerPixel*8;
		enum Cnannels {rChannel, gChannel, bChannel };
		
		m_Image.Create( view.width(), view.height(), 32 );
		void* pBits = m_Image.GetBits();
		int Pitch = m_Image.GetPitch();
		for(int y = 0; y < view.height() ; y++)
		{
			rgb8_image_t::view_t::x_iterator itX = const_cast<rgb8_image_t::view_t::x_iterator>(view.row_begin(y)); // source
			BYTE* pRow = (BYTE*)pBits + Pitch*y; // destination

			BYTE r, g, b;
			for( int x = 0; x < view.width(); x++)
			{
				BYTE* pPixel = (BYTE*)pRow + cBytesPerPixel*x;

				r = itX[x][rChannel];
				g = itX[x][gChannel];
				b = itX[x][bChannel];

				*pPixel = b;
				*(pPixel + 1) = g;
				*(pPixel + 2) = r;
			}
		}
		m_Image.Save(m_FileName.c_str(), guidFileType);
	}// apply
};


//
} // detail
} // gil
} // boost

#endif